package com.comunity_cloud.client.model.communication;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;

import com.comunity_cloud.client.model.Configuration;
import com.comunity_cloud.client.model.MainModel2;
import com.comunity_cloud.client.model.communication.packet.PeerPacket;
import com.comunity_cloud.client.model.communication.packet.PeerPacketHeader;
import com.comunity_cloud.client.model.communication.packet.PeerPacketHeader.Type;


public class PeerConnection extends Thread{

	private long clientId;

	private Socket socket;

	private byte[] headerBuffer = new byte[Configuration.getInstance().readBufferSize];

	private ByteBuffer byteBuffer = ByteBuffer.wrap(headerBuffer);

	private Map<Type,PeerPacketHeader> packetHeaders = new HashMap<PeerPacketHeader.Type, PeerPacketHeader>();	

	@SuppressWarnings("rawtypes")
	PeerPacket peerPacket = new PeerPacket();

	private static Map<Type, Vector<PacketListener<?>>> listeners = new Hashtable<>();

	private byte[] payloadBuffer = new byte[Configuration.getInstance().readBufferSize];

	
	public PeerConnection(Socket socket, long clientId) throws IOException {
		this(socket);
		this.clientId = clientId;
		byte[] clientIdBytes = new byte[8];
		ByteBuffer buffer = ByteBuffer.wrap(clientIdBytes);
		buffer.putLong(MainModel2.getInstance().getClientDTO().getId());
		socket.getOutputStream().write(clientIdBytes);
		socket.getOutputStream().flush();
		
		peerPacket.setClientId(clientId);
	}
	
	public PeerConnection(Socket socket) throws IOException {
		this.socket = socket;
		socket.setReceiveBufferSize(Configuration.getInstance().readBufferSize*2);
	}

	public long getClientId() {
		return clientId;
	}
	
	public void setClientId(long clientId) {
		this.clientId = clientId;
		peerPacket.setClientId(clientId);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void run() {
		while(true) {
			try {
				InputStream in = socket.getInputStream();

				int bytesRead = 0;
				
				byteBuffer.rewind();
				
				while( (bytesRead += in.read(headerBuffer, bytesRead, 8 - bytesRead)) < 8);
				
				
				int headerLength = PeerPacketHeader.headerLengthFromBytes(byteBuffer);

				while( (bytesRead += in.read(headerBuffer, bytesRead, headerLength - bytesRead)) < headerLength);

				int payloadSize = PeerPacketHeader.payloadSizeFromBytes(byteBuffer);
				
				bytesRead = 0;

				if(payloadSize > 0) {
					while( (bytesRead += in.read(payloadBuffer, bytesRead, payloadSize - bytesRead)) < payloadSize);
				}

				Type type = PeerPacketHeader.typeFromBytes(byteBuffer);

				PeerPacketHeader header = packetHeaders.get(type);

				if(header == null) {
					try {
						Class<?> clazz = PeerPacketHeader.TypesMap.get(type);
						header = (PeerPacketHeader) clazz.newInstance();
						packetHeaders.put(type, header);
					} catch (Exception e) {
						e.printStackTrace();
						
					}
				}

				header.fromByteArray(headerBuffer);
				peerPacket.setHeader(header);
				
				if(payloadSize == 0) {
					peerPacket.setPayload(null);
				} else {
					peerPacket.setPayload(payloadBuffer);
				}

				Vector<PacketListener<?>> listenersList = listeners.get(type);
				if(listenersList != null) {
					for (PacketListener<?> packetListener : listenersList) {
						packetListener.packetReceived(peerPacket);
					}
				}
			

			} catch (IOException e) {
//				e.printStackTrace();
			}
			
		}
		
	}

	public static void addPacketListener(Type type, @SuppressWarnings("rawtypes") PacketListener listener) {
		Vector<PacketListener<?>> listenersList = listeners.get(type);
		if(listenersList == null) {
			listenersList = new Vector<>();
			listeners.put(type, listenersList);
		}
		if(!listenersList.contains(listener)) {
			listenersList.add(listener);
		}
	}

	public synchronized void sendPacket(PeerPacket<?> peerPacket) throws IOException {
		byte[] headerBytes = peerPacket.getHeader().asByteArray();
		socket.getOutputStream().write(headerBytes);
		if(peerPacket.getHeader().getPayloadSize() > 0) {
			socket.getOutputStream().write(peerPacket.getPayload(),0,peerPacket.getHeader().getPayloadSize());
		}
		socket.getOutputStream().flush();		
	}
	
}
